package com.example.demo.util;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.ArrayUtils;

import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

public class StringDigest 
{
	private StringDigest() {}

	/**
	 * Generate the password hash with random salt before store it
	 * 
	 * @param password
	 * @return hashed password
	 */
	public static String hash(String password) {
		String itoa64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
		String output = "";
		double d = Math.random();
		int ran_index = (int)Math.floor(d*64) % 64;
		String ran_string = String.valueOf(d);
		char ran_char = itoa64.charAt(ran_index);
		String setting = "$H$";
		setting += ran_char;
		setting += DigestUtils.md5Hex(ran_string).substring(0,8);
		output = hash_crypt_private(password, setting);
		return output;
	}

	/**
	 * Generate the hash with the salt contained in the second parameter.
	 * The result string should be the same as the second parameter if the password is correct  
	 * 
	 * @param password: password input
	 * @param setting: hashed password in native storage
	 * @return hashed password
	 */
	public static String hash_crypt_private(String password, String setting) {
		String output = "*";
		if (!setting.substring(0, 3).equals("$H$")) {return output;}
		char c = setting.charAt(3); c = (char)((c % 7)+3);
		int count = 1 << c;
		String salt = setting.substring(4, 12);
		if (salt.length() != 8) {return output;}
		for (int i=0; i<count; i++) {
			salt = salt + password;
			salt = DigestUtils.md5Hex(salt);
		}
		output = setting.substring(0, 12);
		output += salt.substring(0,22);

		return output;
	}

	public static String base64ToString(String string) throws IOException {
		byte[] bytes = base64ToByte(string);
		return new String(bytes, "UTF-8");
	}

	public static byte[] base64ToByte(String data) throws IOException {
		BASE64Decoder decoder = new BASE64Decoder();
		return decoder.decodeBuffer(data);
	}

	public static String stringToBase64(String string) {
		return byteToBase64(string.getBytes());
	}

	public static String byteToBase64(byte[] data){
		BASE64Encoder endecoder = new BASE64Encoder();
		return endecoder.encode(data);
	}

	/**
	 * Get a 32-character random string
	 * 
	 * @return
	 */
	public static String unique_id() {
		double d = Math.random();
		long millTime = System.currentTimeMillis();
		String ran_string = String.valueOf(millTime) + String.valueOf(d);
		return md5(ran_string);
	}

	/**
	 * Get the 32-character MD5 result
	 * 
	 * @param s: input string
	 * @return
	 */
	public static String md5(String s) {
		return md5(s, 32);
	}

	/**
	 * Get MD5 value of the input string
	 * 
	 * @param s: input string
	 * @param length: output length
	 * @return MD5 result
	 */
	public static String md5(String s, int length) {
		String output = null;
		length = (length > 32 || length < 0) ? 32 : length;

		output = DigestUtils.md5Hex(s);
		return output.substring(0, length);
	}

	public static final char[] digits = {
		'0' , '1' , '2' , '3' , '4' , '5' ,
		'6' , '7' , '8' , '9' , 'a' , 'b' ,
		'c' , 'd' , 'e' , 'f' , 'g' , 'h' ,
		'i' , 'j' , 'k' , 'l' , 'm' , 'n' ,
		'o' , 'p' , 'q' , 'r' , 's' , 't' ,
		'u' , 'v' , 'w' , 'x' , 'y' , 'z'
	};

	/**
	 * Convert the integer to an unsigned number.
	 * toUnsignedString(i, 4) equals to toHexString(i);
	 * 
	 */
	public static String toUnsignedString(long i, int shift) 
	{
		char[] buf = new char[64];
		int charPos = 64;
		int radix = 1 << shift;
		long mask = radix - 1;
		do {
			buf[--charPos] = digits[(int)(i & mask)];
			i >>>= shift;
		} while (i != 0);
		return new String(buf, charPos, (64 - charPos));
	}

	/**
	 * 根据输入的二进制数组生成整数(注: 只处理正数), 二进制高位在右
	 * 例如: 输入 int[] = {1,1,1,1} 输出 15
	 * 
	 * @param bins
	 * @return
	 */
	public static int binsToDecimal(int[] bins)
	{
		int c = 0;
		for(int j=0; j<bins.length; j++) {
			c += (bins[j] > 0)? 1 << j : 0;
		}
		return c;
	}

	/**
	 * 根据输入的十进制正整数, 输出二进制数组, 高位在右
	 * 例如: 输入20, 输出[0, 0, 1, 0, 1]
	 * 
	 * @param decimal
	 * @return
	 */
	public static int[] decimalToBins(int decimal)
	{
		String bin_string = Integer.toBinaryString(decimal);
		int[] bins = new int[bin_string.length()];
		for (int i=0; i<bin_string.length(); i++)
		{
			bins[bin_string.length() - i - 1] = (bin_string.charAt(i) == '0')? 0:1;
		}
		return bins;
	}

	/**
	 * 根据输入的整数数组生成整数(注:只处理正数), 数组中每个数代表二进制中的位数, 不能重复, 重复的数会被忽略
	 * 例如: 输入 int[] = {2, 4} 输出 20
	 * 
	 * 
	 * @param indices
	 * @return
	 */
	public static int binIndicesToDecimal(int[] indices)
	{
		int c = 0;
		int tmp = -1;
		if (indices != null && indices.length > 0)
		{
			Arrays.sort(indices);
			for (int i=0; i<indices.length; i++)
			{
				if (indices[i] == tmp)
					continue;
				else
				{
					tmp = indices[i];
					c += 1 << indices[i];
				}
			}
		}
			
		return c;
	}

	/**
	 * 根据输入的十进制正整数, 输出二进制数组, 数组中每一位的值, 表示二进制中有值的位, 二进制位数从0开始
	 * 例如: 输入 20, 输出 [2, 4]
	 * 
	 * @param decimal
	 * @return
	 */
	public static int[] decimalToBinIndices(int decimal)
	{
		String bin_string = Integer.toBinaryString(decimal);
		int[] bins = new int[bin_string.length()];
		int pos = 0;
		for (int i=bin_string.length() - 1; i >= 0; i--)
		{
			if (bin_string.charAt(i) != '0')
			{
				bins[pos] = bin_string.length() - i - 1;
				pos++;
			}
		}
		return ArrayUtils.subarray(bins, 0, pos);
	}

	/**
	 * 根据输入的二进制位(从0开始)数组, 和预设的二进制长度, 生成所有可能的整数数组
	 * 数组中的数, 必须满足二进制序列在输入的位数上为1
	 * 
	 * 例如: 输入0, 2和4, 输出5, 7, 13, 15
	 * 
	 * @param index
	 * @param length 出于性能考虑, 不能大于5
	 */
	public static Integer[] indexesToPossibleDecimals(int index[], int length)
	{
		if (index == null || index.length == 0)
			return new Integer[0];

		int upper = 1 << length;
		List<Integer> output = new ArrayList<Integer>();
		boolean flag;
		for (int i = 0; i < upper; i++)
		{
			flag = true;
			for (int h=0; h < index.length; h++)
			{
				if ((i >> index[h] & 1) != 1)
				{
					flag = false;
					break;
				}
			}
			if (flag)
			{
				output.add(i);
			}
		}
		return output.toArray(new Integer[]{});
	}

	/**
	 * 根据输入的二进制位(从0开始), 和预设的二进制长度, 生成所有可能的整数数组
	 * 例如: 输入0和4, 输出1,3,5,7,9,11,13,15
	 * 例如: 输入1和4, 输出2,3,6,7,10,11,14,15
	 * 
	 * @param index
	 * @param length
	 */
	public static int[] indexToPossibleDecimals(int index, int length)
	{
		int upper = 1 << length;
		int[] output = new int[upper/2];
		int j = 0;
		for (int i = 0; i < upper; i++)
		{
			if ((i >> index & 1) == 1)
			{
				output[j] = i;
				j++;
			}
		}
		return output;
	}

	/**
	 * Fill the int array with value = index + offset
	 * 
	 * @param size
	 * @param offset
	 * @return
	 */
	public static int[] initialiseIntArray(int size, int offset)
	{
		int[] array = new int[size];
		for (int i=0; i<array.length; i++)
		{
			array[i] = i+offset;
		}
		return array;
	}

	public static Integer[] initializeIntegerArray(int size, int offset)
	{
		Integer[] array = new Integer[size];
		for (int i=0; i<array.length; i++)
		{
			array[i] = i+offset;
		}
		return array;
	}

	public static void main(String[] args) 
	{
		int[] result1 = decimalToBinIndices(20);
		int[] result = decimalToBins(20);
		System.out.println(binIndicesToDecimal(result1));
		System.out.println(binsToDecimal(result));
		result1 = decimalToBinIndices(33);
		result = decimalToBins(33);
		System.out.println(binIndicesToDecimal(result1));
		System.out.println(binsToDecimal(result));
		result1 = decimalToBinIndices(9);
		result = decimalToBins(9);
		System.out.println(binIndicesToDecimal(result1));
		System.out.println(binsToDecimal(result));
		int[] bins = {1,1,1,1,1,1,1,1};
		System.out.println(binsToDecimal(bins));
		System.out.println(binsToDecimal(result));
		bins = new int[]{0,1,1,1, 1, 1, 1, 1, 1, 1};
		System.out.println(binsToDecimal(bins));
		bins = new int[]{0, 0, 0, 1};
		System.out.println(binsToDecimal(bins));

		Integer[] integers = indexesToPossibleDecimals(new int[]{0, 2}, 4);
		for (Integer integer: integers)
			System.out.println(integer);
		/*System.out.println(md5(args[0]));
		System.out.println(hash(args[0])	);*/
	}
}
